﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Scala.Compiler.Binding;
using System.Diagnostics;
using Scala.Compiler.Ast;

namespace Scala.Compiler
{
    public class ScalaScope
    {
        private readonly string _name;
        private ScalaScope _parentScope;
        private IList<string> _nameCheckList;
        private IDictionary<string, ScalaScope> _childScopes;
        private IDictionary<string, ScalaVariable> _variables;
        private IDictionary<string, ScalaType> _types;
        private IDictionary<string, ScalaFunction> _functions;

        private bool _isPackage, _isClass, _isModule, _isFunction, _isBlock;

        public ScalaScope(string name)
        {
            _name = name;
        }

        public string Name { get { return _name; } }
        public string PackageString { get; set; }
        public ScalaType CurrentType { get; set; }
        public ScalaScope ParentScope { get { return _parentScope; } set { _parentScope = value; } }
        public IList<string> NameCheckList
        {
            get
            {
                if (_nameCheckList == null)
                {
                    _nameCheckList = new List<string>();
                    return _nameCheckList;
                }
                return _nameCheckList;
            }
        }
        public IDictionary<string, ScalaScope> ChildScopes
        {
            get
            {
                if (_childScopes == null) return new Dictionary<string, ScalaScope>();
                return _childScopes;
            }
            set { _childScopes = value; }
        }
        public IDictionary<string, ScalaVariable> Variables
        {
            get
            {
                if (_variables == null) return new Dictionary<string, ScalaVariable>();
                return _variables;
            }
            set { _variables = value; }
        }
        public IDictionary<string, ScalaType> Types
        {
            get
            {
                if (_types == null) return new Dictionary<string, ScalaType>(); return _types;
            }
            set { _types = value; }
        }
        public IDictionary<string, ScalaFunction> Functions
        {
            get
            {
                if (_functions == null) return new Dictionary<string, ScalaFunction>();
                return _functions;
            }
            set { _functions = value; }
        }
        public bool IsPackage { get { return _isPackage; } set { _isPackage = value; } }
        public bool IsClass { get { return _isClass; } set { _isClass = value; } }
        public bool IsModule { get { return _isModule; } set { _isModule = value; } }
        public bool IsFunction { get { return _isFunction; } set { _isFunction = value; } }
        public bool IsBlock { get { return _isBlock; } set { _isBlock = value; } }

        #region Creating and Ensuring Variables in the Scope

        internal ScalaVariable CreateVariable(string name, Tree valdef)
        {
            EnsureVariables();
            Debug.Assert(!_variables.ContainsKey(name));
            ScalaVariable variable;
            _variables[name] = variable = new ScalaVariable(name, this,valdef);
            return variable;
        }

        internal ScalaVariable EnsureVariable(string name, Tree valdef)
        {
            ScalaVariable variable;
            if (!TryGetVariable(name, out variable))
            {
                return CreateVariable(name, valdef);
            }
            return variable;
        }

        private void EnsureVariables()
        {
            if (_variables == null)
            {
                _variables = new Dictionary<string, ScalaVariable>(StringComparer.Ordinal);
            }
        }

        internal bool TryGetAnyVariable(string name, out ScalaVariable variable)
        {
            if (TryGetVariable(name, out variable))
            {
                return true;
            }
            else
            {
                for (var sc = this.ParentScope; sc != null; sc = sc.ParentScope)
                {
                    if (sc.TryGetVariable(name, out variable)) return true;
                }
            }
            variable = null;
            return false;
        }

        internal bool TryGetVariable(string name, out ScalaVariable variable)
        {
            if (_variables != null)
            {
                return _variables.TryGetValue(name, out variable);
            }
            else
            {
                variable = null;
                return false;
            }
        }

        #endregion

        #region Creating and Ensuring Types in the Scope

        internal ScalaType CreateType(string name, ModuleDefStatement scalaObject)
        {
            EnsureTypes();
            Debug.Assert(!_types.ContainsKey(name));
            ScalaType type;
            _types[name] = type = new ScalaType(name, scalaObject);
            return type;
        }

        internal ScalaType EnsureType(string name, ModuleDefStatement scalaObject)
        {
            ScalaType type;
            if (!TryGetType(name, out type))
            {
                return CreateType(name, scalaObject);
            }
            return type;
        }

        internal ScalaType CreateType(string name, ClassDefStatement scalaObject)
        {
            EnsureTypes();
            Debug.Assert(!_types.ContainsKey(name));
            ScalaType type;
            _types[name] = type = new ScalaType(name, scalaObject);
            return type;
        }

        internal ScalaType EnsureType(string name, ClassDefStatement scalaObject)
        {
            ScalaType type;
            if (!TryGetType(name, out type))
            {
                return CreateType(name, scalaObject);
            }
            return type;
        }

        private void EnsureTypes()
        {
            if (_types == null)
            {
                _types = new Dictionary<string, ScalaType>(StringComparer.Ordinal);
            }
        }

        internal bool TryGetAnyType(string name, out ScalaType type)
        {
            if (TryGetType(name, out type))
            {
                return true;
            }
            else
            {
                for (var sc = this.ParentScope; sc != null; sc = sc.ParentScope)
                {
                    if (sc.TryGetType(name, out type)) return true;
                }
            }
            type = null;
            return false;
        }

        internal bool TryGetType(string name, out ScalaType type)
        {
            if (_types != null)
            {
                return _types.TryGetValue(name, out type);
            }
            else
            {
                type = null;
                return false;
            }
        }

        #endregion

        #region Creating and Ensuring Functions in the Scope

        internal ScalaFunction CreateFunction(string name, DefDefStatement scalaFunction)
        {
            EnsureFunctions();
            Debug.Assert(!_functions.ContainsKey(name));
            ScalaFunction func;
            _functions[name] = func = new ScalaFunction(name, scalaFunction);
            return func;
        }

        internal ScalaFunction EnsureFunction(string name, DefDefStatement scalaFunction)
        {
            ScalaFunction func;
            if (!TryGetFunc(name, out func))
            {
                return CreateFunction(name, scalaFunction);
            }
            return func;
        }

        private void EnsureFunctions()
        {
            if (_functions == null)
            {
                _functions = new Dictionary<string, ScalaFunction>(StringComparer.Ordinal);
            }
        }

        internal bool TryGetAnyFunc(string name, out ScalaFunction function)
        {
            if (TryGetFunc(name, out function))
            {
                return true;
            }
            else
            {
                for (var sc = this.ParentScope; sc != null; sc = sc.ParentScope)
                {
                    if (sc.TryGetFunc(name, out function)) return true;
                }
            }
            function = null;
            return false;
        }

        internal bool TryGetFunc(string name, out ScalaFunction function)
        {
            if (_functions != null)
            {
                return _functions.TryGetValue(name, out function);
            }
            else
            {
                function = null;
                return false;
            }
        }

        #endregion

        public bool CheckName(string name)
        {
            ScalaFunction func;
            ScalaType tp;
            var ret = TryGetAnyFunc(name, out func);
            ret = ret | TryGetAnyType(name, out tp);
            return ret;
        }

        public bool CheckVariable(string name)
        {
            ScalaVariable variable;
            for (var sc = this; sc != null; sc = sc.ParentScope)
            {
                if (TryGetVariable(name, out variable)) { return true; }
            }
            return false;
        }

        //public bool TryGetScope(string name)
        //{
        //    for (var sc = this; sc != null; sc = sc.ParentScope)
        //    {
        //        if(sc.Name == name) return 
        //    }
        //}
    }
}
